// IzhikevichNetwork.h

#pragma once

using namespace System;
using namespace System::Collections::Generic;

#include "NetworkBase.h"
#include "Stimulus.h"
#include "Connection.h"
#include "PNGData.h"

namespace SpikingNeuronLib
{
	// Find Polychronous Groups computation constants---------------------------
	const int W = 3;					// initial width of polychronous groups
	const double C_rel = 0.95 * C_max;
	const int min_group_path = 7;		// minimal length of a group

	// This class is derived from the work of Eugene Izhikevich (see http://www.izhikevich.org/publications/spnet.htm)
	// With modifications to support the move to a memory-managed environment and some substantial refactoring
	// See the implementation file for more details
	public ref class IzhikevichNetwork : public NetworkBase
	{
	protected:
		int _numSynapses;			// fixed number of synapses per neuron

		array<int>^ _N_pre;

		array<int, 2>^ _post;
		array<int, 2>^ _I_pre;
		array<int, 2>^ _D_pre;
		array<int, 2>^ _firings;
		array<double, 2>^ _s;
		array<double, 2>^ _sd;
		array<short, 2>^ _delays_length;
		array<ArrayRef^, 2>^ _sd_pre;	// Note: _s_pre not used; also, using indirect references, the values for _s_pre and _sd_pre will always be the same

		array<short, 3>^ _delays;

		array<double, 2>^ _LTP;			// LTP plasticity values for each neuron (dimension 0 = neuron index; dimension 1 = delay)
		array<double>^ _LTD;			// LTD plasticity values for each neuron

		// external stimulus firing times for input stimulus and background stimulus
		// for each msec timeslot, the array of neuron indices that fire in that timeslot
		array<array<int>^>^ _stimulusFiringData;	// neuron firing times for the current second for the input stimulus
		array<array<int>^>^ _backgroundFiringData;	// neuron firing times for the current second for the background stimulus

		void Init(int numExcitatory, int numInhibitory, int numSynapses, int maxDelay, unsigned int seed);
		virtual int RunImpl(int totalSeconds, PatternGenerator^ backgroundPattern, IStimulus^ inputPattern) override;
		virtual int RunImpl(int startSeconds, int endSeconds, PatternGenerator^ backgroundPattern, IStimulus^ inputPattern) override;
		int RunNetwork(int startSeconds, int endSeconds, PatternGenerator^ backgroundPattern, IStimulus^ inputPattern);

		// Get all of the network connections as a list of type Connection (preneuron, postneuron, delay, weight)
		List<Connection^>^ IzhikevichNetwork::GetConnections();

		// Polychronous functions--------------------------------
		void SearchCurrentTriplet(String^ tag, int nnum, int npre[W], int &N_polychronous, int polylenmax,
			array<int>^ I_my_pre, array<int>^ D_my_pre,
			array<int>^ group, array<int>^ t_fired, array<int>^ layer, array<int, 2>^ links,
			array<int>^ N_postspikes, array<int, 2>^ I_postspikes, array<int, 2>^ J_postspikes,
			array<int, 2>^ D_postspikes, array<int, 2>^ L_postspikes, array<double, 2>^ C_postspikes, array<double>^ I);

		void SearchCurrentTripletOriginalVersion(String^ tag, double weightLimit, int nnum, int npre[W], int &N_polychronous, int polylenmax,
				array<int>^ I_my_pre, array<int>^ D_my_pre,
				array<int>^ group, array<int>^ t_fired, array<int, 2>^ links,
				array<int>^ N_postspikes, array<int, 2>^ I_postspikes, array<int, 2>^ J_postspikes,
				array<int, 2>^ D_postspikes, array<int, 2>^ L_postspikes,
				array<int>^ inh_list, array<int>^ t_inh_list, array<int>^ pre_list, array<int>^ del_list);

	public:
		// Properties

		property List<Connection^>^ Connections
		{
		public:
			virtual List<Connection^>^ get() override
			{
				return GetConnections();
			}
		}

		// Output connections per neuron
		property int ConnectionsPerNeuron
		{
		public:
			virtual int get() override { return _numSynapses; }
		}

		property int TotalConnections
		{
		public:
			virtual int get() override { return _totalNeurons * _numSynapses; }
		}

		property int ExcitatoryConnections
		{
		public:
			virtual int get() override { return _numExcitatoryNeurons * _numSynapses; }
		}

		property int InhibitoryConnections
		{
		public:
			virtual int get() override { return _numInhibitoryNeurons * _numSynapses; }
		}

		property int SynapsesPerNeuron
		{
		public:
			int get() { return _numSynapses; }
		}

		property array<int, 2>^ PostSynapticConnections
		{
		public:
			array<int, 2>^ get() { return _post; }
			void set(array<int, 2>^ data) { _post = data; }
		}

		property array<double, 2>^ SynapticWeights
		{
		public:
			array<double, 2>^ get() { return _s; }
			void set(array<double, 2>^ data) { _s = data; }
		}

		// Current STDP values for depression
		// Updated each time step
		// See also GetLTPAmplitudes(neuronIndex)
		property array<double>^ LTDAmplitudes
		{
		public:
			array<double>^ get() { return _LTD; }
		}

		// Constructors and Destructors
		IzhikevichNetwork();
		IzhikevichNetwork(int numExcitatory, int numInhibitory, int numSynapses, int maxDelay);
		IzhikevichNetwork(int numExcitatory, int numInhibitory, int numSynapses, int maxDelay, unsigned int seed);
		~IzhikevichNetwork();

		// Methods

		virtual void SaveFiringsData(String^ filePath, bool includeOverflow) override;
		virtual void LoadFiringsData(String^ filePath) override;

		// Network state must not be saved from within a OneSecondTickBeforeCleanup event handler:
		// the cleanup code at the end of each second must be called first or the network state will be corrupt
		// Save the network state from within a OneSecondTickAfterCleanup event handler instead
		virtual void SaveNetworkState(String^ filePath) override;
		virtual void LoadNetworkState(String^ filePath) override;

		/// Get the delay between the specified pre- and post-synaptic neurons
		/// returning zero if there is no such connection
		// Should be in the range 1 to _maxDelay
		int GetDelay(int preNeuron, int postNeuron)
		{
			int delay = 0;

			// search through each offset (via _delays) to find the specified post neuron
			for (int dd = 0; dd < _maxDelay && delay == 0; dd++)
			{
				for (int jj = 0; jj < _delays_length[preNeuron, dd]; jj++)
				{
					if (_post[preNeuron, _delays[preNeuron, dd, jj]] == postNeuron)
					{
						delay = dd;
						break;
					}
				}
			}

			return delay;
		}

		// Load the network state from a CrossbarNetwork
		void LoadCrossbarNetworkState(String^ filePath);

		// This method must be called from within a OneSecondTickBeforeCleanup event handler
		// as code after this event callback cleans up the firing stack
		virtual array<FiringEvent>^ GetFiringsData(bool includeOverflow) override;

		void SavePostData(String^ filePath);
		void LoadPostData(String^ filePath);

		void SaveSynapticData(String^ filePath);
		void LoadSynapticData(String^ filePath);

		void RandomizeWeights();			// Set all synaptic weights to an appropriate random value
		void RandomizeWeights(int range);	// Set all synaptic weights by perturbing the base weight up or down by random values within the specified range

		// Current STDP values for potentiation
		// Progressively set over each second
		// Retrieve at OnOneSecondTickBeforeCleanup
		// See also LTDAmplitudes property
		array<double>^ GetLTPAmplitudes(int neuronIndex);

		// Polychronous functions--------------------------------
		void Shuffle();																// shuffle the synaptic matrices (s and sd)

		// tag argument passes through to FoundPNGEventHandler; can be null
		int FindPolychronousGroupsOriginalVersion(String^ tag, int nnum);						// find neural groups anchored by neuron 'nnum'
		int FindPolychronousGroupsOriginalVersion(String^ tag, double weightLimit, int nnum);		// find neural groups with the specified exclusive lower bound on the synaptic weights

		int FindPolychronousGroups(String^ tag, int nnum);										// find neural groups anchored by neuron 'nnum'
		int FindPolychronousGroupsFromTriplet(String^ tag, int presynaptic[W], int delays[W]);
		int FindPolychronousGroupsFromTriplet(String^ tag, Triplet^ triplet);					// find neural groups initiated by an event triplet
		int FindPolychronousGroupsFromTripletList(String^ tag, List<Triplet^>^ triplets);		// find neural groups initiated by combinations of triplets of the input pattern
	};
}
